/**
 * @file   radio.c
 *
 * @brief    QKits TXRX24G transceiver module
 *
 * CSC 460/560 Real Time Operating Systems - Mantis Cheng
 *
 * @author Scott Craig
 * @author Justin Tanner
 */

#include "radio.h"

/** Configuration data defined in radio.h */
static uint8_t radio_cfg[CFG_VECT_LEN] =
{
    DATA2_W,    /* byte 0 */
    DATA1_W,    /* byte 1 */
    ADDR2,      /* bytes 2 to 6 */
    ADDR1,      /* bytes 7 to 11 */
    ADDRW_CRC,  /* byte 12 */
    MODES,      /* byte 13 */
    RFCH_RXEN   /* byte 14 */
};

/** Buffer for sending and receiving lines destined for UART */
static char radio_buf[RADIO_BUF_LEN];

/* Forward decs */
static inline void delay_125ns(void);
static inline void delay_500ns(void);
static inline void delay_1us(void);
static void put_byte(uint8_t rfbyte);
static void set_standby_mode(void);
static void set_config_mode(void);
static void set_rxtx_mode(void);


/**
 * @brief Delay. Assumes CPU frequency is 8 MHz
 *
 */
static inline void delay_125ns(void)
{
    __asm__ volatile ("nop\n\t"::);
}


/**
 * @brief Delay. Assumes CPU frequency is 8 MHz
 */
static inline void delay_500ns(void)
{
    __asm__ volatile (
        "nop\n\t"
        "nop\n\t"
        "nop\n\t"
        "nop\n\t"
        ::
    );
}


/**
 * @brief Delay. Assumes CPU frequency is 8 MHz
 */
static inline void delay_1us(void)
{
    __asm__ volatile (
        "nop\n\t"
        "nop\n\t"
        "nop\n\t"
        "nop\n\t"
        "nop\n\t"
        "nop\n\t"
        "nop\n\t"
        "nop\n\t"
        ::
    );
}


/**
 * @brief Write one byte into the payload of the radio packet one bit at a time.
 *
 * This is the lowest level write function.
 *
 * @param rfbyte the byte to be loaded into the payload for transmission.
 */
static void put_byte(uint8_t rfbyte)
{
    int shift;

    for(shift = 7; shift >= 0; --shift)
    {
        if(rfbyte & _BV(shift))
            DATA_HIGH();
        else
            DATA_LOW();

        CLK1_HIGH();
        delay_500ns();
        CLK1_LOW();
    }
}


/**
 * @brief Switch the radio into standby mode.
 *
 */
static void set_standby_mode(void)
{
    /* Disable external interrupt */
    EIMSK &= ~_BV(INT4);

    CS_LOW();
    CE_LOW();

    CLK1_LOW();
    DATA_LOW();
}


/**
 * @brief Switch the radio into configuration mode.
 *
 * This is only necessary when
 * initializing the radio or when changing between radio RX and TX modes.
 *
 */
static void set_config_mode(void)
{
    CE_LOW();

    /* Set Data to output */
    DATA_DDR |= _BV(DATA_PINNUM);

    DATA_LOW();
    CLK1_LOW();

    delay_125ns();     /* Td = 50 ns */

    CS_HIGH();

    _delay_us(5);     /* Tcs2data = 5 us */
}


/**
 * @brief Switch the radio into active mode.
 *
 */
static void set_rxtx_mode(void)
{
    CS_LOW();
    delay_125ns(); /* Td = 50 ns */

    DATA_LOW();
    CLK1_LOW();

    CE_HIGH();
    _delay_us(5); /* Tce2data = 5 us */
}


/**
 *  @brief This function is called to configure the radio.
 *
 *  In BC, legal
 *  channel numbers are 0 to 82, or 101 to 119. the range 101 to 119
 *  is usually better, because to other channels compete with wireless
 *  LANs. If the channel is wrong, it will be set to DEFAULT_CHANNEL.
 *
 *  The address is a number in the packet. For this app, both the
 *  sender and receiver will have the same address.

 *
 * @param channel A number corresponding to a legal channel.
 * @param address A 16 bit integer (Eg. 0xABBA) to be this radio's ID.
 * @param rx_enable 0 for transmit, anything else for receive.
 *
 * @return -1 if not a legal channel
 */
int radio_init(uint8_t channel, uint16_t address, uint8_t rx_enable)
{
    uint8_t rxen = (rx_enable > 0) ? 1 : 0;
    int bad_channel = 0;
    uint8_t* txbyte = radio_cfg;
    uint8_t count = CFG_VECT_LEN;

    /* Set up config vector */
    radio_cfg[10] = (uint8_t)(address >> 8);
    radio_cfg[11] = (uint8_t) address;

    if((channel > 82 && channel < 101) || channel > 119)
    {
        channel = DEFAULT_CHANNEL;
        bad_channel = -1;
    }
    radio_cfg[14] = (uint8_t)((channel << 1) | rxen);

    if(rx_enable)
    {
        /* Rising edge trigger on INT4 (1,1) for data ready pin of radio */
        EICRB |= (_BV(ISC41) | _BV(ISC40));
    }

    /* Set up output pins */
    CLK1_DDR |= _BV(CLK1_PINNUM);
    CE_DDR    |= _BV(CE_PINNUM);
    CS_DDR |= _BV(CS_PINNUM);
    DATA_DDR |= _BV(DATA_PINNUM);

    _delay_ms(3); /* Tpd2sby = 3 ms (power-on-to-standby waiting time) */

    set_config_mode();

    while(count)
    {
        put_byte(*txbyte);
        ++txbyte;
        --count;
    }

    set_standby_mode();

    /* Set Data to input or output */
    if(rx_enable)
    {
        DATA_DDR &= ~(_BV(DATA_PINNUM));
    }
    else
    {
        DATA_DDR |= _BV(DATA_PINNUM);
    }


    set_rxtx_mode();

    if(rx_enable)
    {
        EIMSK |= _BV(INT4);
    }

    return bad_channel;
}


/**
 * @brief A function to send a formatted string over the radio, printf style.
 *
 * The string is broken into packet sized chunks, which are sent.
 * Delivery is not guaranteed. Pieces of the string may not
 * arrive. The chunk size is PAYLOAD_BYTES, so strings
 * shorter than that will either arrive or not. The maximum string
 * length is RADIO_BUF_LEN.
 *
 * @param format printf style arguments
 *
 * @return number of bytes sent
 */
int radio_print(const char* format, ...)
{
    int bytes_written;
    int bytes_to_write;
    int bytes_this_packet;
    int bytes_left_over;
    char* buf_ptr = radio_buf;

    va_list args;
    va_start(args, format);

    bytes_written = vsnprintf(radio_buf, RADIO_BUF_LEN, format, args);

    if(bytes_written > RADIO_BUF_LEN) bytes_written = RADIO_BUF_LEN;

    va_end(args);

    bytes_to_write = bytes_written;
    buf_ptr = radio_buf;

    while(bytes_to_write)
    {
        set_rxtx_mode();

        if(bytes_to_write > PAYLOAD_BYTES)
        {
            bytes_this_packet = PAYLOAD_BYTES;
            bytes_left_over = 0;
            bytes_to_write -= PAYLOAD_BYTES;
        }
        else
        {
            bytes_this_packet = bytes_to_write;
            bytes_left_over = PAYLOAD_BYTES - bytes_this_packet;
            bytes_to_write = 0;
        }

        put_byte(radio_cfg[10]);
        put_byte(radio_cfg[11]);

        while(bytes_this_packet--)  /* Payload */
        {
            put_byte(*buf_ptr);
            ++ buf_ptr;
        }

        while(bytes_left_over--)  /* Padding */
        {
            put_byte((uint8_t)0);
        }

        /* start shockburst transmission */
        DATA_LOW();
        CE_LOW();
        /*_delay_us(500);    // Tsby2txSB + Toa = 195 us + 256 bits / 1 Mbps */
        _delay_ms(DELAY_MS_BETWEEN_PACKETS);
    }

    return bytes_written;
}


/**
 * @brief Read one byte from the payload of the radio packet one bit at a time.
 *
 * This is the lowest level read function.
 *
 * @return single byte from the radio
 */
uint8_t radio_get_byte(void)
{
    int shift;
    unsigned char rfbyte = 0x0;

    for(shift = 7; shift >= 0; --shift)
    {
        CLK1_HIGH();

        if (DATA_PIN & _BV(DATA_PINNUM))
            rfbyte |= _BV(shift);

        CLK1_LOW();
    }

    return rfbyte;
}






